using System;
using System.IO;
using System.Xml;
using System.Collections;

namespace Server.Engines.Reports
{
	public class StaffHistory : PersistableObject
	{
		#region Type Identification
		public static readonly PersistableType ThisTypeID = new PersistableType( "stfhst", new ConstructCallback( Construct ) );

		private static PersistableObject Construct()
		{
			return new StaffHistory();
		}

		public override PersistableType TypeID{ get{ return ThisTypeID; } }
		#endregion

		private PageInfoCollection m_Pages;
		private QueueStatusCollection m_QueueStats;

		private Hashtable m_UserInfo;
		private Hashtable m_StaffInfo;

		public PageInfoCollection Pages{ get{ return m_Pages; } set{ m_Pages = value; } }
		public QueueStatusCollection QueueStats{ get{ return m_QueueStats; } set{ m_QueueStats = value; } }

		public Hashtable UserInfo{ get{ return m_UserInfo; } set{ m_UserInfo = value; } }
		public Hashtable StaffInfo{ get{ return m_StaffInfo; } set{ m_StaffInfo = value; } }

		public void AddPage( PageInfo info )
		{
			lock ( SaveLock )
				m_Pages.Add( info );

			info.History = this;
		}

		public StaffHistory()
		{
			m_Pages = new PageInfoCollection();
			m_QueueStats = new QueueStatusCollection();

			m_UserInfo = new Hashtable( StringComparer.OrdinalIgnoreCase );
			m_StaffInfo = new Hashtable( StringComparer.OrdinalIgnoreCase );
		}

		public StaffInfo GetStaffInfo( string account )
		{
			lock ( RenderLock )
			{
				if ( account == null || account.Length == 0 )
					return null;

				StaffInfo info = m_StaffInfo[account] as StaffInfo;

				if ( info == null )
					m_StaffInfo[account] = info = new StaffInfo( account );

				return info;
			}
		}

		public UserInfo GetUserInfo( string account )
		{
			if ( account == null || account.Length == 0 )
				return null;

			UserInfo info = m_UserInfo[account] as UserInfo;

			if ( info == null )
				m_UserInfo[account] = info = new UserInfo( account );

			return info;
		}

		public static readonly object RenderLock = new object();
		public static readonly object SaveLock = new object();

		public void Save()
		{
			lock ( SaveLock )
			{
				string path = Path.Combine( Core.BaseDirectory, "staffHistory.xml" );
				PersistanceWriter pw = new XmlPersistanceWriter( path, "Staff" );

				pw.WriteDocument( this );

				pw.Close();
			}
		}

		public void Load()
		{
			string path = Path.Combine( Core.BaseDirectory, "staffHistory.xml" );

			if ( !File.Exists( path ) )
				return;

			PersistanceReader pr = new XmlPersistanceReader( path, "Staff" );

			pr.ReadDocument( this );

			pr.Close();
		}

		public override void SerializeChildren( PersistanceWriter op )
		{
			for ( int i = 0; i < m_Pages.Count; ++i )
				m_Pages[i].Serialize( op );

			for ( int i = 0; i < m_QueueStats.Count; ++i )
				m_QueueStats[i].Serialize( op );
		}

		public override void DeserializeChildren( PersistanceReader ip )
		{
			DateTime min = DateTime.Now - TimeSpan.FromDays( 8.0 );

			while ( ip.HasChild )
			{
				PersistableObject obj = ip.GetChild();

				if ( obj is PageInfo )
				{
					PageInfo pageInfo = obj as PageInfo;

					pageInfo.UpdateResolver();

					if ( pageInfo.TimeSent >= min || pageInfo.TimeResolved >= min )
					{
						m_Pages.Add( pageInfo );
						pageInfo.History = this;
					}
					else
					{
						pageInfo.Sender = null;
						pageInfo.Resolver = null;
					}
				}
				else if ( obj is QueueStatus )
				{
					QueueStatus queueStatus = obj as QueueStatus;

					if ( queueStatus.TimeStamp >= min )
						m_QueueStats.Add( queueStatus );
				}
			}
		}

		public StaffInfo[] GetStaff()
		{
			StaffInfo[] staff = new StaffInfo[m_StaffInfo.Count];
			int index = 0;

			foreach ( StaffInfo staffInfo in m_StaffInfo.Values )
				staff[index++] = staffInfo;

			return staff;
		}

		public void Render( ObjectCollection objects )
		{
			lock ( RenderLock )
			{
				objects.Add( GraphQueueStatus() );

				StaffInfo[] staff = GetStaff();

				BaseInfo.SortRange = TimeSpan.FromDays( 7.0 );
				Array.Sort( staff );

				objects.Add( GraphHourlyPages( m_Pages, PageResolution.None, "New pages by hour", "graph_new_pages_hr" ) );
				objects.Add( GraphHourlyPages( m_Pages, PageResolution.Handled, "Handled pages by hour", "graph_handled_pages_hr" ) );
				objects.Add( GraphHourlyPages( m_Pages, PageResolution.Deleted, "Deleted pages by hour", "graph_deleted_pages_hr" ) );
				objects.Add( GraphHourlyPages( m_Pages, PageResolution.Canceled, "Canceled pages by hour", "graph_canceled_pages_hr" ) );
				objects.Add( GraphHourlyPages( m_Pages, PageResolution.Logged, "Logged-out pages by hour", "graph_logged_pages_hr" ) );

				BaseInfo.SortRange = TimeSpan.FromDays( 1.0 );
				Array.Sort( staff );

				objects.Add( ReportTotalPages( staff, TimeSpan.FromDays(  1.0 ), "1 Day" ) );
				objects.AddRange( (PersistableObject[])ChartTotalPages( staff, TimeSpan.FromDays(  1.0 ), "1 Day", "graph_daily_pages" ) );

				BaseInfo.SortRange = TimeSpan.FromDays( 7.0 );
				Array.Sort( staff );

				objects.Add( ReportTotalPages( staff, TimeSpan.FromDays(  7.0 ), "1 Week" ) );
				objects.AddRange( (PersistableObject[])ChartTotalPages( staff, TimeSpan.FromDays(  7.0 ), "1 Week", "graph_weekly_pages" ) );

				BaseInfo.SortRange = TimeSpan.FromDays( 30.0 );
				Array.Sort( staff );

				objects.Add( ReportTotalPages( staff, TimeSpan.FromDays( 30.0 ), "1 Month" ) );
				objects.AddRange( (PersistableObject[])ChartTotalPages( staff, TimeSpan.FromDays( 30.0 ), "1 Month", "graph_monthly_pages" ) );

				for ( int i = 0; i < staff.Length; ++i )
					objects.Add( GraphHourlyPages( staff[i] ) );
			}
		}

		public static int GetPageCount( StaffInfo staff, DateTime min, DateTime max )
		{
			return GetPageCount( staff.Pages, PageResolution.Handled, min, max );
		}

		public static int GetPageCount( PageInfoCollection pages, PageResolution res, DateTime min, DateTime max )
		{
			int count = 0;

			for ( int i = 0; i < pages.Count; ++i )
			{
				if ( res != PageResolution.None && pages[i].Resolution != res )
					continue;

				DateTime ts = pages[i].TimeResolved;

				if ( ts >= min && ts < max )
					++count;
			}

			return count;
		}

		private BarGraph GraphQueueStatus()
		{
			int[] totals = new int[24];
			int[] counts = new int[24];

			DateTime max = DateTime.Now;
			DateTime min = max - TimeSpan.FromDays( 7.0 );

			for ( int i = 0; i < m_QueueStats.Count; ++i )
			{
				DateTime ts = m_QueueStats[i].TimeStamp;

				if ( ts >= min && ts < max )
				{
					DateTime date = ts.Date;
					TimeSpan time = ts.TimeOfDay;

					int hour = time.Hours;

					totals[hour] += m_QueueStats[i].Count;
					counts[hour]++;
				}
			}

			BarGraph barGraph = new BarGraph( "Average pages in queue", "graph_pagequeue_avg", 10, "Time", "Pages", BarGraphRenderMode.Lines );

			barGraph.FontSize = 6;

			for ( int i = 7; i <= totals.Length+7; ++i )
			{
				int val;

				if ( counts[i%totals.Length] == 0 )
					val = 0;
				else
					val = (totals[i%totals.Length] + (counts[i%totals.Length] / 2)) / counts[i%totals.Length];

				int realHours = i%totals.Length;
				int hours;

				if ( realHours == 0 )
					hours = 12;
				else if ( realHours > 12 )
					hours = realHours - 12;
				else
					hours = realHours;

				barGraph.Items.Add( hours + (realHours >= 12 ? " PM" : " AM"), val );
			}

			return barGraph;
		}

		private BarGraph GraphHourlyPages( StaffInfo staff )
		{
			return GraphHourlyPages( staff.Pages, PageResolution.Handled, "Average pages handled by " + staff.Display, "graphs_" + staff.Account.ToLower() + "_avg" );
		}

		private BarGraph GraphHourlyPages( PageInfoCollection pages, PageResolution res, string title, string fname )
		{
			int[] totals = new int[24];
			int[] counts = new int[24];
			
			DateTime[] dates = new DateTime[24];

			DateTime max = DateTime.Now;
			DateTime min = max - TimeSpan.FromDays( 7.0 );

			bool sentStamp = ( res == PageResolution.None );

			for ( int i = 0; i < pages.Count; ++i )
			{
				if ( res != PageResolution.None && pages[i].Resolution != res )
					continue;

				DateTime ts = ( sentStamp ? pages[i].TimeSent : pages[i].TimeResolved );

				if ( ts >= min && ts < max )
				{
					DateTime date = ts.Date;
					TimeSpan time = ts.TimeOfDay;

					int hour = time.Hours;

					totals[hour]++;

					if ( dates[hour] != date )
					{
						counts[hour]++;
						dates[hour] = date;
					}
				}
			}

			BarGraph barGraph = new BarGraph( title, fname, 10, "Time", "Pages", BarGraphRenderMode.Lines );

			barGraph.FontSize = 6;

			for ( int i = 7; i <= totals.Length+7; ++i )
			{
				int val;

				if ( counts[i%totals.Length] == 0 )
					val = 0;
				else
					val = (totals[i%totals.Length] + (counts[i%totals.Length] / 2)) / counts[i%totals.Length];

				int realHours = i%totals.Length;
				int hours;

				if ( realHours == 0 )
					hours = 12;
				else if ( realHours > 12 )
					hours = realHours - 12;
				else
					hours = realHours;

				barGraph.Items.Add( hours + (realHours >= 12 ? " PM" : " AM"), val );
			}

			return barGraph;
		}

		private Report ReportTotalPages( StaffInfo[] staff, TimeSpan ts, string title )
		{
			DateTime max = DateTime.Now;
			DateTime min = max - ts;

			Report report = new Report( title + " Staff Report", "400" );

			report.Columns.Add( "65%", "left", "Staff Name" );
			report.Columns.Add( "35%", "center", "Page Count" );

			for ( int i = 0; i < staff.Length; ++i )
				report.Items.Add( staff[i].Display, GetPageCount( staff[i], min, max ) );

			return report;
		}

		private PieChart[] ChartTotalPages( StaffInfo[] staff, TimeSpan ts, string title, string fname )
		{
			DateTime max = DateTime.Now;
			DateTime min = max - ts;

			PieChart staffChart = new PieChart( title + " Staff Chart", fname + "_staff", true );

			int other = 0;

			for ( int i = 0; i < staff.Length; ++i )
			{
				int count = GetPageCount( staff[i], min, max );

				if ( i < 12 && count > 0 )
					staffChart.Items.Add( staff[i].Display, count );
				else
					other += count;
			}

			if ( other > 0 )
				staffChart.Items.Add( "Other", other );

			PieChart resChart = new PieChart( title + " Resolutions", fname + "_resol", true );

			int countTotal = GetPageCount( m_Pages, PageResolution.None, min, max );
			int countHandled = GetPageCount( m_Pages, PageResolution.Handled, min, max );
			int countDeleted = GetPageCount( m_Pages, PageResolution.Deleted, min, max );
			int countCanceled = GetPageCount( m_Pages, PageResolution.Canceled, min, max );
			int countLogged = GetPageCount( m_Pages, PageResolution.Logged, min, max );
			int countUnres = countTotal - ( countHandled + countDeleted + countCanceled + countLogged );

			resChart.Items.Add( "Handled", countHandled );
			resChart.Items.Add( "Deleted", countDeleted );
			resChart.Items.Add( "Canceled", countCanceled );
			resChart.Items.Add( "Logged Out", countLogged );
			resChart.Items.Add( "Unresolved", countUnres );

			return new PieChart[]{ staffChart, resChart };
		}
	}
}